package com.why.cachecloud.schedule.jobs;

import com.why.cachecloud.configuration.constant.AppStatusEnum;
import com.why.cachecloud.dao.AppStatsDao;
import com.why.cachecloud.dao.AppTrafficStatDao;
import com.why.cachecloud.entity.AppDesc;
import com.why.cachecloud.entity.AppStats;
import com.why.cachecloud.entity.AppTrafficStat;
import com.why.cachecloud.entity.WarnConfig;
import com.why.cachecloud.service.alert.WarnConfigService;
import com.why.cachecloud.service.alert.WarnType;
import com.why.cachecloud.service.custom.alert.DingTalkComponent;
import com.why.cachecloud.web.service.AppService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AppTrafficStatService {

    @Resource
    private AppTrafficStatDao appTrafficStatDao;

    @Resource
    private AppStatsDao appStatsDao;

    @Resource
    protected AppService appService;

    @Resource
    private WarnConfigService warnConfigService;

    @Resource
    private DingTalkComponent dingTalkComponent;

    public void trafficStat() {
        log.info("流量统计开始。。。");
        List<Integer> statusList = Collections.singletonList(AppStatusEnum.STATUS_PUBLISHED.getStatus());

        List<AppDesc> appList = appService.queryListByStatus(statusList);
        if (CollectionUtils.isEmpty(appList)) {
            log.info("流量统计结束,没有可用的应用。。。");
            return;
        }

        List<AppTrafficStat> appTrafficStatList = new ArrayList<>();

        for (AppDesc appDesc : appList) {
            AppTrafficStat stat = trafficStat(appDesc);
            if (stat == null) {
                continue;
            }
            appTrafficStatList.add(stat);
        }
        saveOrUpdate(appTrafficStatList);
        log.info("流量统计结束。。。");
    }

    private void saveOrUpdate(List<AppTrafficStat> statList) {
        if (statList.isEmpty()) {
            return;
        }
        List<AppTrafficStat> dbStatList = appTrafficStatDao.queryAll();
        if (CollectionUtils.isEmpty(dbStatList)) {
            appTrafficStatDao.batchSave(statList);
            return;
        }

        List<Long> appIdList = dbStatList.stream().map(AppTrafficStat::getAppId).collect(Collectors.toList());

        Map<Boolean, List<AppTrafficStat>> map = statList.stream().collect(Collectors.groupingBy(at -> appIdList.contains(at.getAppId())));
        List<AppTrafficStat> saveList = map.get(Boolean.FALSE);
        if (CollectionUtils.isNotEmpty(saveList)) {
            appTrafficStatDao.batchSave(saveList);
            log.info("流量统计写入{}条新的数据。。。", statList.size());
        }
        List<AppTrafficStat> updateList = map.get(Boolean.TRUE);
        if (CollectionUtils.isNotEmpty(updateList)) {
            for (AppTrafficStat appTrafficStat : updateList) {
                appTrafficStatDao.updateByAppId(appTrafficStat);
                log.info("流量统计更新应用[{}]的流量数据。。。", appTrafficStat.getAppId());
            }
        }
    }

    private AppTrafficStat trafficStat(AppDesc appDesc) {
        List<AppStats> appStatsByHourList = appStatsDao.getAppStatsHourCommandList(appDesc.getAppId());
        if (appStatsByHourList == null || appStatsByHourList.isEmpty()) {
            return null;
        }
        // 一小时内小于100条的不进行统计,防止流量较少导致的异常告警
        appStatsByHourList = appStatsByHourList.stream().filter(a -> a.getCommandCount() > 100).collect(Collectors.toList());
        if (appStatsByHourList == null || appStatsByHourList.isEmpty()) {
            return null;
        }

        // 小时时间戳转换为日期时间戳
        Map<Long, List<AppStats>> day2AppStatsMap = appStatsByHourList.stream().collect(Collectors.groupingBy(a -> a.getCollectTime() / 100));

        // 每一天最大流量
        List<Long> maxList = new ArrayList<>();
        for (Map.Entry<Long, List<AppStats>> dataEntry : day2AppStatsMap.entrySet()) {
            // 每小时的流量集合
            List<AppStats> statsList = dataEntry.getValue();

            List<Long> valueList = statsList.stream().map(AppStats::getCommandCount).sorted().collect(Collectors.toList());
            double avg = valueList.stream().mapToLong(a -> a).average().getAsDouble();
            // 排除异常流量: 超过平均流量5倍数据的流量为异常流量
            List<Long> valList = valueList.stream().filter(v -> v < avg * 5).sorted().collect(Collectors.toList());
//            List<Long> valList = valueList.stream().sorted().collect(Collectors.toList());
            long max = 0;
            if (CollectionUtils.isNotEmpty(valList)) {
                max = valList.get(valList.size() - 1);
            }
            if (max > 0) {
                maxList.add(max);
            }
        }
        if (maxList.isEmpty()) {
            return null;
        }

        List<Long> maxSortedList = maxList.stream().sorted().collect(Collectors.toList());
        Long maxMax = maxSortedList.get(maxSortedList.size() - 1);
        double maxAvgDouble = maxSortedList.stream().mapToLong(a -> a).average().getAsDouble();
        long maxAvg = new BigDecimal(String.valueOf(maxAvgDouble)).setScale(0, RoundingMode.CEILING).longValue();

        if (maxMax == 0 || maxAvg == 0) {
            return null;
        }
        return new AppTrafficStat(null, appDesc.getAppId(), maxAvg, maxMax, day2AppStatsMap.size(), LocalDateTime.now());
    }

    public void checkAppTraffic() {
        log.info("App流量检查开始...");
        List<Integer> statusList = Collections.singletonList(AppStatusEnum.STATUS_PUBLISHED.getStatus());

        List<AppDesc> appList = appService.queryListByStatus(statusList);
        if (CollectionUtils.isEmpty(appList)) {
            log.info("App流量检查,没有可用的应用。。。");
            return;
        }
        appList.forEach(appDesc -> checkAppTraffic(appDesc.getAppId()));
        log.info("App流量检查结束...");
    }

    public void checkAppTraffic(long appId) {
        // 分钟转换为小时
        //yyyyMMddHH
        long collectTimeHour = Long.parseLong(LocalDateTime.now().plusHours(-1).format(DateTimeFormatter.ofPattern("yyyyMMddHH")));

        AppTrafficStat appTrafficStat = appTrafficStatDao.queryByAppId(appId);
        // 没有之前的流量数据无法比较
        if (appTrafficStat == null) {
            log.warn("APP流量检查,没有之前流量数据无法进行比较;appId:{},检测的收集时间为:{}", appId, collectTimeHour);
            return;
        }
        // 数据统计小于10天的可能不精准也不进行比较
        if (appTrafficStat.getDays() < 10) {
            log.warn("APP流量检查,数据统计小于10天的可能不精准也不进行比较;appId:{},检测的收集时间为:{}", appId, collectTimeHour);
            return;
        }

        Long count = appStatsDao.countHourCommandBy(appId, collectTimeHour);
        if (count == null || count < 10000L) {
            log.warn("APP流量检查,count记录为NULL或者小于1W不进行比较;appId:{},检测的收集时间为:{}", appId, collectTimeHour);
            return;
        }

        long avgMax = appTrafficStat.getAvgMax();
        long maxMax = appTrafficStat.getMaxMax();
        // 平均值的2倍,最大值增幅的30%；如果超过了说明流量异常，需要及时检查
        long avgMaxThreshold = (long) (avgMax * (1 + 2));
        long maxMaxThreshold = (long) (maxMax * (1 + 0.3));

        if (count < avgMaxThreshold && count < maxMaxThreshold) {
            log.warn("APP流量检查,小于阈值退出;appId:{},检测的收集时间为:{},count:{},avgMaxThreshold:{},maxMaxThreshold:{}", appId, collectTimeHour, count, avgMaxThreshold, maxMaxThreshold);
            return;
        }

        WarnConfig warnConfig = warnConfigService.queryConfig(appId, WarnType.traffic);
        if (warnConfig == null) {
            log.warn("APP流量检查,没有获取到流量告警配置内容;appId:{},检测的收集时间为:{},count:{},avgMaxThreshold:{},maxMaxThreshold:{}", appId, collectTimeHour, count, avgMaxThreshold, maxMaxThreshold);
            return;
        }

        StringBuilder warnInfo = new StringBuilder("应用:").append(appId).append(",在时间点").append(collectTimeHour);
        warnInfo.append("流量为: ")
                .append(count)
                .append("条,相较于前")
                .append(appTrafficStat.getDays())
                .append("天");
        if (count > avgMaxThreshold) {
            int rate = (int) (((count - avgMax) * 100.0 / avgMax) + 0.5);
            warnInfo.append("每天流量最大值的均值: ")
                    .append(avgMax)
                    .append("条,增幅为: ")
                    .append(rate)
                    .append("%");
        } else {
            int rate = (int) (((count - maxMax) * 100.0 / maxMax) + 0.5);
            warnInfo.append("流量的最大值: ")
                    .append(maxMax)
                    .append("条,增幅为: ")
                    .append(rate)
                    .append("%");
        }
        String msg = warnInfo.toString();
        log.info("APP流量检查,appId[{}]进行告警,内容:{}", appId, msg);
        dingTalkComponent.sendDingTalk(warnConfig, appId, msg);
    }

}